import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router" import { createStore } from "solid-js/store" import { createMemo, For, Show } from "solid-js" import { Modal } from "~/component/modal" import { Billing } from "@opencode-ai/console-core/billing.js" import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js" import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { Actor } from "@opencode-ai/console-core/actor.js" import { Subscription } from "@opencode-ai/console-core/subscription.js" import { LiteData } from "@opencode-ai/console-core/lite.js" import { withActor } from "~/context/auth.withActor" import { queryBillingInfo } from "../../common" import styles from "./lite-section.module.css" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError } from "~/lib/form-error" import { IconAlipay, IconUpi } from "~/component/icon" const queryLiteSubscription = query(async (workspaceID: string) => { "use server" return withActor(async () => { const row = await Database.use((tx) => tx .select({ userID: LiteTable.userID, rollingUsage: LiteTable.rollingUsage, weeklyUsage: LiteTable.weeklyUsage, monthlyUsage: LiteTable.monthlyUsage, timeRollingUpdated: LiteTable.timeRollingUpdated, timeWeeklyUpdated: LiteTable.timeWeeklyUpdated, timeMonthlyUpdated: LiteTable.timeMonthlyUpdated, timeCreated: LiteTable.timeCreated, lite: BillingTable.lite, }) .from(BillingTable) .innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID)) .where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted))) .then((r) => r[0]), ) if (!row) return null const limits = LiteData.getLimits() const mine = row.userID === Actor.userID() return { mine, useBalance: row.lite?.useBalance ?? false, rollingUsage: Subscription.analyzeRollingUsage({ limit: limits.rollingLimit, window: limits.rollingWindow, usage: row.rollingUsage ?? 0, timeUpdated: row.timeRollingUpdated ?? new Date(), }), weeklyUsage: Subscription.analyzeWeeklyUsage({ limit: limits.weeklyLimit, usage: row.weeklyUsage ?? 0, timeUpdated: row.timeWeeklyUpdated ?? new Date(), }), monthlyUsage: Subscription.analyzeMonthlyUsage({ limit: limits.monthlyLimit, usage: row.monthlyUsage ?? 0, timeUpdated: row.timeMonthlyUpdated ?? new Date(), timeSubscribed: row.timeCreated, }), } }, workspaceID) }, "lite.subscription.get") function formatResetTime(seconds: number, i18n: ReturnType) { const days = Math.floor(seconds / 86400) if (days >= 1) { const hours = Math.floor((seconds % 86400) / 3600) return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}` } const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) if (hours >= 1) return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds") return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` } const createLiteCheckoutUrl = action( async (workspaceID: string, successUrl: string, cancelUrl: string, method?: "alipay" | "upi") => { "use server" return json( await withActor( () => Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string, data: undefined, })), workspaceID, ), { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, ) }, "liteCheckoutUrl", ) const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => { "use server" return json( await withActor( () => Billing.generateSessionUrl({ returnUrl }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string, data: undefined, })), workspaceID, ), { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, ) }, "liteSessionUrl") const setLiteUseBalance = action(async (form: FormData) => { "use server" const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } const useBalance = (form.get("useBalance") as string | null) === "true" return json( await withActor(async () => { await Database.use((tx) => tx .update(BillingTable) .set({ lite: useBalance ? { useBalance: true } : {}, }) .where(eq(BillingTable.workspaceID, workspaceID)), ) return { error: undefined } }, workspaceID).catch((e) => ({ error: e.message as string })), { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, ) }, "setLiteUseBalance") export function LiteSection() { const params = useParams() const i18n = useI18n() const language = useLanguage() const billingInfo = createAsync(() => queryBillingInfo(params.id!)) const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked) const lite = createAsync(() => queryLiteSubscription(params.id!)) const sessionAction = useAction(createSessionUrl) const sessionSubmission = useSubmission(createSessionUrl) const checkoutAction = useAction(createLiteCheckoutUrl) const checkoutSubmission = useSubmission(createLiteCheckoutUrl) const useBalanceSubmission = useSubmission(setLiteUseBalance) const [store, setStore] = createStore({ loading: undefined as undefined | "session" | "checkout" | "alipay" | "upi", showModal: false, }) const busy = createMemo(() => !!store.loading) async function onClickSession() { setStore("loading", "session") const result = await sessionAction(params.id!, window.location.href) if (result.data) { window.location.href = result.data return } setStore("loading", undefined) } async function onClickSubscribe(method?: "alipay" | "upi") { setStore("loading", method ?? "checkout") const result = await checkoutAction(params.id!, window.location.href, window.location.href, method) if (result.data) { window.location.href = result.data return } setStore("loading", undefined) } return ( <>

{i18n.t("workspace.lite.black.message")}

{(sub) => (

{i18n.t("workspace.lite.subscription.message")}

{i18n.t("workspace.lite.subscription.selectProvider")}{" "} {i18n.t("common.learnMore")} .
{i18n.t("workspace.lite.subscription.rollingUsage")} {sub().rollingUsage.usagePercent}%
{i18n.t("workspace.lite.subscription.resetsIn")}{" "} {formatResetTime(sub().rollingUsage.resetInSec, i18n)}
{i18n.t("workspace.lite.subscription.weeklyUsage")} {sub().weeklyUsage.usagePercent}%
{i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)}
{i18n.t("workspace.lite.subscription.monthlyUsage")} {sub().monthlyUsage.usagePercent}%
{i18n.t("workspace.lite.subscription.resetsIn")}{" "} {formatResetTime(sub().monthlyUsage.resetInSec, i18n)}

{i18n.t("workspace.lite.subscription.useBalance")}

)}

{i18n.t("workspace.lite.other.message")}

{(part) => { if (part === "{{price}}") return {i18n.t("workspace.lite.promo.price")} return part }}

{i18n.t("workspace.lite.promo.modelsTitle")}

  • Kimi K2.5
  • Kimi K2.6
  • GLM-5
  • GLM-5.1
  • MiMo-V2-Pro
  • MiMo-V2-Omni
  • MiMo-V2.5-Pro
  • MiMo-V2.5
  • MiniMax M2.5
  • MiniMax M2.7
  • Qwen3.5 Plus
  • Qwen3.6 Plus
  • DeepSeek V4 Pro
  • DeepSeek V4 Flash

{i18n.t("workspace.lite.promo.footer")}

setStore("showModal", false)} title={i18n.t("workspace.lite.promo.selectMethod")} >
) }